﻿// ===============================================================================
//  Copyright © ymind.net. All rights reserved .
//  官方网站：http://ymind.net/
//  版权所有：彦铭工作室 老陈出品
// ===============================================================================

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using Lcsoft.Data.RDBMS.Results;

namespace Lcsoft.Data.RDBMS
{
	/// <summary>
	/// 表示数据库管理器。
	/// </summary>
	[ComVisible(true)]
	public class DatabaseManager : IDisposable
	{
		private const string _SQL_PROVIDER = "System.Data.SqlClient";

		/// <summary>
		/// 获取表示系统使用的最小时间取值的字符串。
		/// </summary>
		public static readonly DateTime SqlMinimumDateTime = new DateTime(1900, 1, 1, 0, 0, 0);

		/// <summary>
		/// 获取表示系统使用的最大时间取值的字符串。
		/// </summary>
		public static readonly DateTime SqlMaximumDateTime = new DateTime(9999, 12, 31, 23, 59, 59);

		private readonly Dictionary<string, DbParameter> _parameterCache = new Dictionary<string, DbParameter>();
		private readonly DbProviderFactory _providerFactory;

		/// <summary>
		/// 初始化 <see cref="DatabaseManager"/> 的新实例。
		/// </summary>
		/// <param name="sqlStatement">要执行的文本命令。默认值为空字符串 ("")。</param>
		/// <param name="connectionString">用来建立初始连接的连接字符串。该连接字符串的确切内容取决于此连接的特定数据源。默认值为空字符串。</param>
		/// <param name="sqlProvider">提供程序的固定名称。</param>
		/// <param name="isStoredProcedure">如果设置为 true 则表示要执行的文本命令是存储过程。</param>
		public DatabaseManager(string sqlStatement, string connectionString, string sqlProvider, bool isStoredProcedure)
		{
			this.CommandTimeout = 15;
			this.OutputParameters = new OutputParameterCollection();
			this._providerFactory = DbProviderFactories.GetFactory(String.IsNullOrEmpty(sqlProvider) ? _SQL_PROVIDER : sqlProvider);

			this._CreateCommand(sqlStatement, isStoredProcedure);
			this._CreateConnection(connectionString);
			this._SetProviderType(sqlProvider);
		}

		/// <summary>
		/// 获取一个值，该值表示当前查询执行的文本命令是否是存储过程。
		/// </summary>
		internal bool IsStoredProcedure { get; private set; }

		/// <summary>
		/// 获取 <see cref="System.Data.Common.DbConnection"/> 的实例。
		/// </summary>
		internal DbConnection DbConnection { get; private set; }

		/// <summary>
		/// 获取 <see cref="System.Data.Common.DbCommand"/> 的实例。
		/// </summary>
		internal DbCommand DbCommand { get; private set; }

		/// <summary>
		/// 获取 <see cref="System.Data.Common.DbParameter"/> 的集合。
		/// </summary>
		internal DbParameter[] SqlParameters { get; set; }

		/// <summary>
		/// 获取要执行的文本命令。默认值为空字符串 ("")。
		/// </summary>
		internal string SqlStatement { get; private set; }

		/// <summary>
		/// 获取包含输出参数的实例的集合。
		/// </summary>
		internal OutputParameterCollection OutputParameters { get; set; }

		/// <summary>
		/// 获取 <see cref="System.Data.Common.DbTransaction"/> 的实例。
		/// </summary>
		internal DbTransaction Transaction
		{
			set
			{
				this.DbConnection = value.Connection;
				this.DbCommand.Transaction = value;
			}
		}

		/// <summary>
		/// 获取用来建立初始连接的连接字符串。该连接字符串的确切内容取决于此连接的特定数据源。默认值为空字符串。
		/// </summary>
		internal string ConnectionString { get; private set; }

		/// <summary>
		/// 获取提供程序的固定名称。
		/// </summary>
		internal string SqlProvider { get; private set; }

		/// <summary>
		/// 设置在建立连接时终止尝试并生成错误之前所等待的时间。 
		/// </summary>
		internal int ConnectionTimeout
		{
			get { return this.DbConnection.ConnectionTimeout; }
		}

		/// <summary>
		/// 设置或获取在终止执行命令的尝试并生成错误之前的等待时间。
		/// </summary>
		internal int CommandTimeout { get; set; }

		#region IDisposable 成员

		/// <summary>
		/// 关闭并释放由 <see cref="DatabaseManager"/> 使用的所有资源。
		/// </summary>
		public void Dispose()
		{
			this.Close();

			GC.SuppressFinalize(this);
		}

		#endregion

		private void _AddToOutParameterCollection(string parameterName) { this.OutputParameters.Add(parameterName, new OutputParameter(parameterName, null)); }

		private void _SetProviderType(string providerType) { this.SqlProvider = providerType; }

		private void _CreateCommand(string sqlStatement, bool isStoredProcedure)
		{
			this.DbCommand = this._providerFactory.CreateCommand();

			if (this.DbCommand == null) throw new Exception("无法创建 System.Data.Common.DbCommand 类的实例。");

			this.DbCommand.CommandType = isStoredProcedure ? CommandType.StoredProcedure : CommandType.Text;
			this.DbCommand.CommandText = sqlStatement;
			this.IsStoredProcedure = isStoredProcedure;
			this.SqlStatement = sqlStatement;
		}

		private void _CreateConnection(string connectionString)
		{
			this.DbConnection = this._providerFactory.CreateConnection();

			if (this.DbConnection == null) throw new Exception("无法创建 System.Data.Common.DbConnection 类的实例。");

			this.DbConnection.ConnectionString = connectionString;
			this.ConnectionString = connectionString;
		}

		private static void _SetParameterValue(IDataParameter param, object value)
		{
			param.Value = DBNull.Value;

			if (value != null && !_IsEmptyGuid(param.DbType, value) && (param.Direction == ParameterDirection.Input || param.Direction == ParameterDirection.InputOutput)) param.Value = value;
			else if (!_IsEmptyGuid(param.DbType, value) && param.Direction == ParameterDirection.InputOutput) param.Value = "0";
		}

		private static bool _IsEmptyGuid(DbType dbType, object value) { return dbType == DbType.Guid && value is Guid && (Guid)value == Guid.Empty; }

		private static void _SetParameterSize(IDbDataParameter param, int size)
		{
			if (size == 0) return;

			if (param.DbType == DbType.String || param.DbType == DbType.AnsiString || param.DbType == DbType.StringFixedLength || param.DbType == DbType.AnsiStringFixedLength) param.Size = size;
		}

		private void _AddParameters()
		{
			if (this.DbConnection is SqlConnection && this.IsStoredProcedure)
			{
				this._DeriveParameters();
				this._MapDerivedParameters(null);
			}
			else if (this._parameterCache.Any()) foreach (var kvp in this._parameterCache) this.DbCommand.Parameters.Add(kvp.Value);
		}

		private void _MapDerivedParameters(DbParameter[] parameters)
		{
			var paramArray = parameters ?? this.SqlParameters;

			if (this._parameterCache == null || this._parameterCache.Count == 0) return;

			foreach (var param in paramArray)
			{
				var parameter = param;

				//this._parameterCache.FirstOrDefault(p =>
				//{
				//    if (p.Key == parameter.ParameterName)
				//    {
				//        var thisParam = p.Value;

				//        if (thisParam.Direction == ParameterDirection.Input || thisParam.Direction == ParameterDirection.InputOutput) this.DbCommand.Parameters[parameter.ParameterName].Value = p.Value.Value;
				//        else
				//        {
				//            p.Value.Direction = ParameterDirection.Output;

				//            this.DbCommand.Parameters[parameter.ParameterName].Value = p.Value.Value ?? "0";
				//        }

				//        return true;
				//    }

				//    return false;
				//});

				foreach (var p in this._parameterCache)
				{
					if (p.Key != parameter.ParameterName) continue;

					var thisParam = p.Value;

					if (thisParam.Direction == ParameterDirection.Input || thisParam.Direction == ParameterDirection.InputOutput) this.DbCommand.Parameters[parameter.ParameterName].Value = p.Value.Value;
					else
					{
						p.Value.Direction = ParameterDirection.Output;

						this.DbCommand.Parameters[parameter.ParameterName].Value = p.Value.Value ?? "0";
					}

					break;
				}
			}
		}

		private void _DeriveParameters()
		{
			SqlCommandBuilder.DeriveParameters((SqlCommand)this.DbCommand);

			if (this.DbCommand.Parameters.Count <= 0) return;

			this.SqlParameters = new DbParameter[this.DbCommand.Parameters.Count];
			this.DbCommand.Parameters.CopyTo(this.SqlParameters, 0);
		}

		/// <summary>
		/// 执行查询，并返回查询所返回的结果集中第一行的第一列。所有其他的列和行将被忽略。
		/// </summary>
		/// <typeparam name="T">指定查询结果的对象的类型。</typeparam>
		/// <param name="outputParameters">在方法返回时包含输出参数的实例的集合。</param>
		/// <returns>返回指定类型的对象的实例。</returns>
		public T ExecuteScalar<T>(out OutputParameterCollection outputParameters)
		{
			T scalarNum;

			try
			{
				this.OpenConnection();

				this._AddParameters();

				scalarNum = (T)this.DbCommand.ExecuteScalar();
				outputParameters = this.GetHydratedOutputParameters();
			}
			finally
			{
				this.Close();
			}

			return scalarNum;
		}

		/// <summary>
		/// 对连接对象执行 SQL 语句。
		/// </summary>
		/// <param name="procParameterMapper">一个参数为 <see cref="System.Data.Common.DbCommand"/> 的 Action 委托。</param>
		/// <param name="outputParameters">在方法返回时包含输出参数的实例的集合。</param>
		public void ExecuteNonQuery(Action<DbCommand> procParameterMapper, out OutputParameterCollection outputParameters)
		{
			this.OpenConnection();

			this._DeriveParameters();

			procParameterMapper.Invoke(this.DbCommand);

			this.DbCommand.ExecuteNonQuery();

			outputParameters = this.GetHydratedOutputParameters();

			this.Close();
		}

		/// <summary>
		/// 对连接对象执行 SQL 语句。
		/// </summary>
		public void ExecuteNonQuery()
		{
			OutputParameterCollection outputParameters;

			this.ExecuteNonQuery(out outputParameters);
		}

		/// <summary>
		/// 对连接对象执行 SQL 语句。
		/// </summary>
		/// <param name="procParameterMapper">一个参数为 <see cref="System.Data.Common.DbCommand"/> 的 Action 委托。</param>
		public void ExecuteNonQuery(Action<DbCommand> procParameterMapper)
		{
			OutputParameterCollection outputParameters;

			this.ExecuteNonQuery(procParameterMapper, out outputParameters);
		}

		/// <summary>
		/// 对连接对象执行 SQL 语句。
		/// </summary>
		/// <param name="outputParameters">在方法返回时包含输出参数的实例的集合。</param>
		public void ExecuteNonQuery(out OutputParameterCollection outputParameters)
		{
			try
			{
				this.OpenConnection();

				this._AddParameters();

				this.DbCommand.ExecuteNonQuery();

				outputParameters = this.GetHydratedOutputParameters();
			}
			finally
			{
				this.Close();
			}
		}

		/// <summary>
		/// 获取已赋值的输出参数的集合。
		/// </summary>
		/// <returns>返回已复制的输出参数的集合。</returns>
		internal OutputParameterCollection GetHydratedOutputParameters()
		{
			if (this.OutputParameters.Count > 0)
			{
				this.CloseConnection();

				for (var i = 0; i < this.OutputParameters.Count; i++)
				{
					var oParam = this.OutputParameters.GetValue(i);

					if (null != oParam) this.OutputParameters.SetValue(oParam.Name, this.DbCommand.Parameters[oParam.Name].Value);
				}

				return this.OutputParameters;
			}

			return null;
		}

		/// <summary>
		/// 执行查询，并返回查询所返回的结果集中第一行的第一列。所有其他的列和行将被忽略。
		/// </summary>
		/// <typeparam name="T">指定查询结果的对象的类型。</typeparam>
		/// <returns>返回指定类型的对象的实例。</returns>
		public T ExecuteScalar<T>()
		{
			OutputParameterCollection outputParameters;

			return this.ExecuteScalar<T>(out outputParameters);
		}

		/// <summary>
		/// 提取 <see cref="System.Data.Common.DbDataReader"/> 的实例。
		/// </summary>
		/// <returns>返回 <see cref="System.Data.Common.DbDataReader"/> 的实例。</returns>
		public DbDataReader FetchReader()
		{
			this.OpenConnection();

			this._AddParameters();

			return this.DbCommand.ExecuteReader(CommandBehavior.CloseConnection);
		}

		/// <summary>
		/// 提取 <see cref="System.Data.DataSet"/> 的实例。
		/// </summary>
		/// <returns>返回 <see cref="System.Data.DataSet"/> 的实例。</returns>
		public DataSet FetchDataSet()
		{
			this.OpenConnection();

			this._AddParameters();

			var ds = new DataSet {
				Locale = CultureInfo.InvariantCulture
			};
			var da = this._providerFactory.CreateDataAdapter();

			if (da == null) throw new Exception("无法创建 System.Data.Common.DbDataAdapter 类的示例。");

			da.SelectCommand = this.DbCommand;
			da.Fill(ds);

			this.Close();

			return ds;
		}

		/// <summary>
		/// 关闭与数据库的连接，并释放由 <see cref="DatabaseManager"/> 使用的所有资源。
		/// </summary>
		public void Close()
		{
			this.CloseConnection();
			this.CloseCommand();
		}

		/// <summary>
		/// 关闭与数据库的连接。这是关闭任何打开连接的首选方法。
		/// </summary>
		public void CloseConnection() { if (this.DbConnection != null && this.DbConnection.State == ConnectionState.Open && this.DbCommand.Transaction == null) this.DbConnection.Close(); }

		/// <summary>
		/// 关闭并释放由 <see cref="DatabaseManager"/> 使用的所有资源。
		/// </summary>
		public void CloseCommand() { this.DbCommand.Dispose(); }

		/// <summary>
		/// 为查询添加一个 <see cref="OutputParameter"/>。
		/// </summary>
		/// <param name="parameterName"><see cref="OutputParameter"/> 的名称。默认值为空字符串 ("")。</param>
		/// <param name="parameterValue">一个 <see cref="System.Object"/>，它是该参数的值。默认值为 null。</param>
		/// <param name="parameterType"><see cref="System.Data.DbType"/> 值之一。默认值为 <see cref="System.Data.DbType.String"/>。</param>
		/// <param name="size">列中数据的最大大小（以字节为单位）。默认值是从参数值推导出的。</param>
		public void AddOutParameter(string parameterName, object parameterValue, DbType parameterType, int size) { this.AddParameter(parameterName, parameterValue, parameterType, size, ParameterDirection.Output); }

		/// <summary>
		/// 为查询添加一个 <see cref="OutputParameter"/>。
		/// </summary>
		/// <param name="parameterName"><see cref="OutputParameter"/> 的名称。默认值为空字符串 ("")。</param>
		/// <param name="parameterValue">一个 <see cref="System.Object"/>，它是该参数的值。默认值为 null。</param>
		public void AddOutParameter(string parameterName, object parameterValue) { this.AddParameter(parameterName, parameterValue, null, 0, ParameterDirection.Output); }

		/// <summary>
		/// 为查询添加一个 <see cref="OutputParameter"/>。
		/// </summary>
		/// <param name="parameterName"><see cref="OutputParameter"/> 的名称。默认值为空字符串 ("")。</param>
		/// <param name="parameterType"><see cref="System.Data.DbType"/> 值之一。默认值为 <see cref="System.Data.DbType.String"/>。</param>
		public void AddOutParameter(string parameterName, DbType parameterType) { this.AddParameter(parameterName, null, parameterType, 0, ParameterDirection.Output); }

		/// <summary>
		/// 为查询添加一个 <see cref="System.Data.Common.DbParameter"/>。
		/// </summary>
		/// <param name="parameterName"><see cref="System.Data.Common.DbParameter"/> 的名称。默认值为空字符串 ("")。</param>
		/// <param name="parameterValue">一个 <see cref="System.Object"/>，它是该参数的值。默认值为 null。</param>
		public void AddParameter(string parameterName, object parameterValue)
		{
			if (!(this.DbConnection is SqlConnection)) throw new SimpleSqlException("只有 Sql Server 数据库才可使用此重载方法：AddParameter(string parameterName, object parameterValue)");
			if (parameterValue == null) throw new SimpleSqlException("参数 parameterValue 的值不能为空。");

			var paramName = parameterName.StartsWith("@") ? parameterName : String.Concat("@", parameterName);

			if (this._parameterCache.ContainsKey(paramName)) return;

			var p = this.DbCommand.CreateParameter();
			p.ParameterName = paramName;
			p.Value = parameterValue;
			this._parameterCache.Add(paramName, p);
		}

		/// <summary>
		/// 为查询添加一个 <see cref="System.Data.Common.DbParameter"/>。
		/// </summary>
		/// <param name="parameterName"><see cref="System.Data.Common.DbParameter"/> 的名称。默认值为空字符串 ("")。</param>
		/// <param name="parameterValue">一个 <see cref="System.Object"/>，它是该参数的值。默认值为 null。</param>
		/// <param name="parameterType"><see cref="System.Data.DbType"/> 值之一。默认值为 <see cref="System.Data.DbType.String"/>。</param>
		/// <param name="paramSize">列中数据的最大大小（以字节为单位）。默认值是从参数值推导出的。</param>
		public void AddParameter(string parameterName, object parameterValue, DbType parameterType, int paramSize) { this.AddParameter(parameterName, parameterValue, parameterType, paramSize, ParameterDirection.Input); }

		/// <summary>
		/// 为查询添加一个 <see cref="System.Data.Common.DbParameter"/>。
		/// </summary>
		/// <param name="parameterName"><see cref="System.Data.Common.DbParameter"/> 的名称。默认值为空字符串 ("")。</param>
		/// <param name="value">一个 <see cref="System.Object"/>，它是该参数的值。默认值为 null。</param>
		/// <param name="paramType"><see cref="System.Data.DbType"/> 值之一。默认值为 <see cref="System.Data.DbType.String"/>。</param>
		/// <param name="paramSize">列中数据的最大大小（以字节为单位）。默认值是从参数值推导出的。</param>
		/// <param name="direction"><see cref="System.Data.ParameterDirection"/> 值之一。默认值为 Input。</param>
		public void AddParameter(string parameterName, object value, DbType? paramType, int paramSize, ParameterDirection direction)
		{
			if (direction == ParameterDirection.Output || direction == ParameterDirection.InputOutput) this._AddToOutParameterCollection(parameterName);

			var param = this._providerFactory.CreateParameter();

			if (param == null) throw new Exception("无法创建 System.Data.Common.DbParameter 类的示例。");

			param.ParameterName = parameterName;

			if (paramType.HasValue) param.DbType = paramType.Value;
			if (param.DbType == DbType.DateTime) value = CheckSqlDateTime(Convert.ToDateTime(value));

			param.Direction = direction;
			_SetParameterSize(param, paramSize);
			_SetParameterValue(param, value);
			this._parameterCache.Add(parameterName, param);
		}

		/// <summary>
		/// 检查时间的取值范围并返回用于 Sql Server 的安全的时间。
		/// </summary>
		/// <param name="dateTime">指定要检查的时间。</param>
		/// <returns>如果 <paramref name="dateTime"/> 早于 <see cref="SqlMinimumDateTime"/>，则返回用于 <see cref="SqlMinimumDateTime"/>；<br />
		/// 如果 <paramref name="dateTime"/> 晚于 <see cref="SqlMaximumDateTime"/>，则返回用于 <see cref="SqlMaximumDateTime"/>；<br />
		/// 如果 <paramref name="dateTime"/> 在 <see cref="SqlMinimumDateTime"/> 和 <see cref="SqlMaximumDateTime"/> 之间，则返回 <paramref name="dateTime"/>。</returns>
		public static DateTime CheckSqlDateTime(DateTime dateTime) { return dateTime < SqlMinimumDateTime ? SqlMinimumDateTime : (dateTime > SqlMaximumDateTime ? SqlMaximumDateTime : dateTime); }

		/// <summary>
		/// 使用 <see cref="System.Data.Common.DbConnection.ConnectionString"/> 所指定的设置打开数据库连接。
		/// </summary>
		public void OpenConnection()
		{
			this.DbCommand.CommandTimeout = this.CommandTimeout;
			this.DbCommand.Connection = this.DbConnection;

			if (null != this.DbConnection && this.DbConnection.State == ConnectionState.Closed) this.DbConnection.Open();
		}

		/// <summary>
		/// 创建 <see cref="System.Data.Common.DbTransaction"/> 的新实例。
		/// </summary>
		/// <returns>返回一个 <see cref="System.Data.Common.DbTransaction"/> 的实例。</returns>
		internal DbTransaction CreateTransaction()
		{
			this.DbConnection.Open();

			return this.DbConnection.BeginTransaction();
		}
	}
}
